﻿/// <reference path="../utilities/FXDom.js" />
/// <reference path="../utilities/Markup.js" />
/// <reference path="../utilities/Post.js" />
/// <reference path="../utilities/Xpcom.js" />
///--------------------------------------------------------------------
/// <file name="QuickCaption.js">
///   Provides enhancements for the view forum page that enable
///   moderators to quickly add captions to threads.
/// </file>
///--------------------------------------------------------------------

NAMFox.ns(function() { with (NAMFox) {

this.QuickCaption = function(_$, htmlDocument, forumId) {
    /// <summary>Initializes a new instance of the QuickEdit class.</summary>
    /// <param name="_$" type="FXDom">
    ///   The FXDom selector that allows this QuickEdit instance
    ///   to select DOM elements from the page itself.
    /// </param>
    /// <param name="htmlDocument" type="Document">
    ///   The HTML Document object for the current page.
    /// </param>
    /// <param name="forumId" type="Number">
    ///   The ID of the forum to which Quick Caption is being added.
    /// </param>
    
    this._$ = _$;
    this._htmlDocument = htmlDocument;
    this._forumId = forumId;
};

QuickCaption.modOnlyExpr = /<input type="checkbox" name="moderator_only" id="moderator_only_checkbox" value="Y"(.*?)\/>/;
QuickCaption.signatureExpr = /<input type="checkbox" name="disable_sigs" id="disable_sigs_checkbox" value="Y"(.*?)\/>/;
QuickCaption.subheaderExpr = /<textarea name="subheader".*?>([\s\S]*?)<\/textarea>/;
QuickCaption.textboxExpr = /#(.*?)#/g;
QuickCaption.threadIdExpr = /(?:public_html|forums)\/\d+\/t(\d+)/;

// Lookups used to translate from an image to its autodelete equivalent and vice versa.
QuickCaption.toAutodeleteImageMap = {
    "http://i.neoseeker.com/f/neo/folder_lock.gif": "http://i.neoseeker.com/f/neo/folder_lock_delete.gif",
    "http://i.neoseeker.com/f/neo/folder_new.gif": "http://i.neoseeker.com/f/neo/folder_new_delete.gif",
    "http://i.neoseeker.com/f/neo/folder_nonew.gif": "http://i.neoseeker.com/f/neo/folder_nonew_delete.gif",
    "http://i.neoseeker.com/f/neo/folder_floating_nonew.gif": "http://i.neoseeker.com/f/neo/folder_floating_nonew.gif",
    "http://i.neoseeker.com/f/neo/folder_floating_new.gif": "http://i.neoseeker.com/f/neo/folder_floating_new.gif",
    "http://i.neoseeker.com/f/neo/folder_floating_locked.gif": "http://i.neoseeker.com/f/neo/folder_floating_locked.gif"
};

QuickCaption.fromAutodeleteImageMap = {
    "http://i.neoseeker.com/f/neo/folder_lock_delete.gif": "http://i.neoseeker.com/f/neo/folder_lock.gif",
    "http://i.neoseeker.com/f/neo/folder_new_delete.gif": "http://i.neoseeker.com/f/neo/folder_new.gif",
    "http://i.neoseeker.com/f/neo/folder_nonew_delete.gif": "http://i.neoseeker.com/f/neo/folder_nonew.gif",
    "http://i.neoseeker.com/f/neo/folder_floating_nonew.gif": "http://i.neoseeker.com/f/neo/folder_floating_nonew.gif",
    "http://i.neoseeker.com/f/neo/folder_floating_new.gif": "http://i.neoseeker.com/f/neo/folder_floating_new.gif",
    "http://i.neoseeker.com/f/neo/folder_floating_locked.gif": "http://i.neoseeker.com/f/neo/folder_floating_locked.gif"
};

QuickCaption.toAutodeleteImage = function(img) {
    return QuickCaption.toAutodeleteImageMap[img] || img;
};

QuickCaption.fromAutodeleteImage = function(img) {
    return QuickCaption.fromAutodeleteImageMap[img] || img;
};

QuickCaption.prototype = {
    _beginSubmit: function(thread) {
        /// <summary>Begins the submission process in the UI.</summary>
        
        $FX(thread.tableCell).find(".namfox-quick-caption-status-container").css("display", "block");
    },
    
    _endSubmit: function(thread) {
        /// <summary>Ends the submission process in the UI.</summary>
        
        $FX(thread.tableCell).find(".namfox-quick-caption-status-container").css("display", "none");
        $FX(thread.tableCell).find(".namfox-quick-caption-submit-icon").css("display", "none");
        $FX(thread.tableCell).find(".namfox-quick-caption-edit-icon").css("display", "inline");
        this._reportStatus("", thread);
    },

    _onTextBoxKeyPress: function(event, threadRow) {
        if (event.keyCode === 13) {
            this._onFormSubmitted(event, threadRow);
        }
    },
    
    _onError: function(xhr, causedByTimeout, thread, caller) {
        /// <summary>Calls when an error occurs in submitting data.</summary>
        /// <param name="caller" type="String">
        ///   A short description of the caller.
        /// </param>
        
        if (caller === "get-caption" && xhr.status >= 2) {
            this._htmlDocument.location.href = this._htmlDocument.location.href;
        }
        else {
            thread.threadDataRequested = false;
            
            // If we're submitting a request, then either the request for thread data
            // or the request to send the post caption failed.
            if (thread.inSubmitStage) {
                this._endSubmit(thread);
                this._reportStatus("Thread properties could not be submitted. Please try again.", thread);
                this._$("#namfox-quick-caption-status").attr("className", "namfox-error");
            }
            else {
                this._endSubmit(thread);
                this._reportStatus("Could not retrieve existing data for the thread. Please try again.", thread);
                this._$("#namfox-quick-caption-status").attr("className", "namfox-error");
            }
        }
        
        thread.inSubmitStage = false;
        $.info("Quick Caption request failed during " + caller);
    },
    
    _onFormSubmitted: function(event, threadRow) {
        /// <summary>
        ///   Occurs when the user submits the quick caption form.
        /// </summary>
        /// <param name="threadData">
        ///   Provides contextual data about the thread
        ///   whose caption was submitted.
        /// </param>

        this._$(".drop-down ul").css("display", "none");
        
        var thread = threadRow.thread;
        if (thread.inSubmitStage) {
            return;
        }

        threadRow.updateThread();

        var that = this;
        function changesEvaluated(hasChanges) {
            if (!hasChanges) {
                that._updateThreadUI(threadRow);
                return;
            }

            thread.inSubmitStage = true;
        
            that._beginSubmit(thread);
            that._reportStatus("Retrieving thread data...", thread);
        
            that._retrieveThreadData(threadRow);
            
            // Wait till we retrieve the thread data on the other thread.
            if (ManualResetEvent.isSupported()) {
                if (!thread.waitHandle.wait(20000)) {
                    // If we can't get the data, then abort.
                    // The error callback for retrieving thread data
                    // will show the appropriate status.
                    $.verbose("Failed waiting for wait handle." + new Date().toString());
                    return;
                }

                // If the ManualResetEvent is not supported, we'll submit the caption from
                // the callback used in _retrieveThreadData.
                that._submitQuickCaption(threadRow);
            }
        }
        
        threadRow.hasChanges(changesEvaluated);
    },
    
    _onNewCaptionRetrieved: function(xhr, threadRow) {
        /// <summary>
        ///   Occurs when a caption was posted successfully to the server.
        /// </summary>
        /// <param name="threadData">
        ///   Provides contextual data about the thread
        ///   whose caption was posted.
        /// </param>
        
        // Get caption data from forum. Set the HTML.
        var captionHtml = xhr.responseText.match(
            new RegExp(
                '<a href="(?:.*?public_html/|/forums/)' + this._forumId + '/t' + threadRow.thread.threadId +
                '[\\s\\S]*?<br /><small>(.*?)</small></td>'
            )
        )[1];
        
        this._updateThreadUI(threadRow, captionHtml);
    },

    _onThreadDataRetrieved: function(xhr, threadRow) {
        /// <summary>
        ///   Called once the thread properties page has been retrieved,
        ///   so that posting to the caption page can submit the proper
        ///   values for thread subheader, mod-only, and other properties.
        /// </summary>
        /// <param name="thread">
        ///   Provides contextual data about the thread
        ///   whose caption was posted.
        /// </param>
        
        $.verbose("QUICK CAPTION | _onThreadDataRetrieved called.");

        var thread = threadRow.thread;
        var response = xhr.responseText;
        $.verbose(response);
        thread.modOnly = $.contains(response.match(QuickCaption.modOnlyExpr)[1], "CHECKED");
        thread.disableSignatures = $.contains(response.match(QuickCaption.signatureExpr)[1], "CHECKED");
        thread.subheader = response.match(QuickCaption.subheaderExpr)[1];
        thread.threadDataRequested = false;
        
        thread.waitHandle.set();
        
        if (!ManualResetEvent.isSupported()) {
            this._submitQuickCaption(threadRow);
        }

        $.verbose("QUICK CAPTION | _onThreadDataRetrieved ending.");
    },
    
    _onEditInvoked: function(event, threadRow) {
        /// <summary>
        ///   Called when the user wants to edit the properties of a particular thread.
        /// </summary>
        /// <param name="thread">
        ///   Provides contextual data about the thread
        ///   whose caption was posted.
        /// </param>

        // Prevent the click from reaching the table cell so the list element
        // with all the default captions becomes hidden.
        event.stopPropagation();

        var captions = this._isBugForum ? QuickCaption.forumSiteBugReportsTemplate : QuickCaption.forumCaptions[this._forumId];

        threadRow.enterEditMode(captions);

        var that = this;
        $FX(threadRow.rowElement).find("input").click(function(event) {
            if (event.keyCode === 13) {
                that._onFormSubmitted(event, threadRow);
            }
        });
        
        if (!this._isBugForum && ManualResetEvent.isSupported()) {
            this._retrieveThreadData(threadRow);
        }
    },

    _retrieveThreadData: function(threadRow) {
        // Retrieve existing data (subheader, mod-only, etc.)
        
        var thread = threadRow.thread;
        if (thread.threadDataRequested) {
            $.verbose("Thread data already requested for thread " + thread.threadId);
            return;
        }
        
        $.verbose("Attempting to retrieve thread data for quick caption for thread " + thread.threadId);
        thread.threadDataRequested = true;
        thread.waitHandle = new ManualResetEvent();
        $.get(
            $.url.forum("/index.php?fn=edit_thread&t=" + thread.threadId),
            null,
            $.createCallback(this._onThreadDataRetrieved, this, threadRow),
            $.createCallback(this._onError, this, thread, "get-thread-data"),
            $.cacheHandling.bypassCache
        );
    },
    
    _reportStatus: function(status, thread) {
        $FX(thread.tableCell).find(".namfox-quick-caption-status").html(status);
    },
    
    _updateThreadUI: function(threadRow, caption) {
        /// <summary>
        ///   Sets the new caption and thread title for a particular thread.
        /// </summary>
        
        threadRow.updateUI(caption);

        threadRow.thread.inSubmitStage = false;
        this._endSubmit(threadRow.thread);
    },
    
    _submitQuickCaption: function(threadRow) {
        /// <summary>
        /// Submits the quick caption. This function joins the different
        /// code paths taken for browsers that can't use ManualResetEvents
        /// and those that can.
        /// </summary>
        /// <param name="thread" type="Thread">The thread object for which to submit the edit.</param>
        
        var thread = threadRow.thread;
        var callbacks = 0;
        thread.requiredCallbacks = 1;
        // Submit captions
        $.verbose("QUICK CAPTION | _submitQuickCaption called.");

        this._reportStatus("Submitting new thread properties...", thread);
        
        if (this._isBugForum) {
            $.assert(thread.bugCaptionValue, "The thread should have a caption value associated with it.");

            var postData = {
                "bug_status": thread.bugCaptionValue,
                "do": "update_bug",
                "update": "Update"
            };
            
            thread.requiredCallbacks = 2;
            $.post(
                thread.href,
                postData,
                onCaptionPosted,
                $.createCallback(this._onError, this, thread, "post-caption")
            );
        }

        var postData = {
            "function": "edit_thread",
            "thread_caption": $.urlEncode(thread.caption),
            "threadid": thread.threadId,
            "threadtitle": $.urlEncode(thread.subject),
            "subheader": $.urlEncode(thread.subheader),
            "submit": true
        };
            
        if (thread.disableSignatures) {
            postData.disable_sigs = "Y";
        }
            
        if (thread.modOnly) {
            postData.moderator_only = "Y";
        }
            
        var autodeletion = thread.autodeletion;
            
        if (autodeletion !== -1) {
            if (autodeletion === 0) {
                postData.reset_deletion_date = true;
            }
            else {
                postData.autodeletion = autodeletion;
            }
        }
            
        $.post(
            $.url.forum("/index.php"),
            postData,
            onCaptionPosted,
            $.createCallback(this._onError, this, thread, "post-caption")
        );
        
        var that = this;
        function onCaptionPosted(xhr) {
            if (++callbacks >= thread.requiredCallbacks) {
                // If the content has HTML inputs (i.e. text boxes),
                // then we can't trust it enough to put it directly
                // on the page. Instead, as long as the content isn't
                // empty, we have to retrieve the new caption through
                // another GET request on the forum itself.
                if (threadRow.thread.needsCaptionHtml) {
                    var markup = thread.caption;
                
                    if ($.trim(markup)) {
                        that._reportStatus("Getting new caption...", thread);
                        $.get(
                            that._htmlDocument.location.href,
                            null,
                            $.createCallback(that._onNewCaptionRetrieved, that, threadRow),
                            $.createCallback(that._onError, that, thread, "get-caption"),
                            $.cacheHandling.bypassCache
                        );
                    }
                    else {
                        that._updateThreadUI(threadRow, "");
                    }
                }
                else {
                    that._updateThreadUI(threadRow, threadRow.thread.captionHtml);
                }
            }
        }
    },
    
    attach: function() {
        /// <summary>Attaches the Quick Caption UI to this page.</summary>
        
        if (this._$("input[value=mass_thread_change]").length === 0) {
            // You're not a moderator...bye!
            return;
        }
        
        this._isBugForum = this._$("#bug_status_filter").length > 0;
        
        var html = '';
        html += '<img class="namfox-quick-caption-edit-icon" src="chrome://namfox/skin/posting/edit-icon.png" />';
        html += '<img class="namfox-quick-caption-submit-icon" src="chrome://namfox/skin/posting/check-icon.png" />';
        this._$("span.thread_stars").before(html);

        var html = '';
        html += '<div class="namfox-quick-caption-status-container">';
        html += '  <img';
        html += '    class="namfox-quick-caption-loading-image"';
        html += '    src="chrome://namfox/skin/posting/loading-eef0f9.gif" />';
        html += '  <span class="namfox-quick-caption-status"></span>';
        html += '</div>';
        this._$("a.subject").parent().append(html);
        
        $FX.each(
            this._$("a.subject"),
            function(i, link) {
                var tableCell = link.parentNode;
                var lastChild = tableCell.lastChild;

                var that = this;
                function markupReceived(caption) {
                    var thread = {
                        caption: caption,
                        tableCell: tableCell,
                        threadId: link.href.match(QuickCaption.threadIdExpr)[1],
                        href: link.href,
                        subject: $FX(link).find("b").html()
                    };

                    var threadRow = new ThreadRow(thread, that);

                    var callback = $.createCallback(that._onEditInvoked, that, threadRow);
                    $FX(tableCell).dblclick(callback);
                    $FX(tableCell).find("img.namfox-quick-caption-edit-icon").click(callback);
                    $FX(tableCell).find("img.namfox-quick-caption-submit-icon").click($.createCallback(that._onFormSubmitted, that, threadRow));
                }

                MarkupTranslator.getMarkupFromHtml(this._htmlDocument, $FX(tableCell).find("small").html(), markupReceived);
            },
            this
        );

        var that = this;
        $FX(this._htmlDocument.body).click(function(event) {
            var node = event.target;

            // Close the drop down if clicking on the drop-down itself, not one of its items.
            while (node && !$.contains(" " + node.className + " ", " selection ")) {
                node = node.parentNode;
            }

            if (node) {
                var list = $FX(node.parentNode).find("ul")
                
                if (list.css("display") === "block") {
                    list.attr("data-preserveExpansion", "true");
                }
            }

            that._$(".drop-down ul[data-preserveExpansion!=true]").css("display", "none");

            if (list) {
                list.attr("data-preserveExpansion", "");
            }
        });
    }
};

function ThreadRow(thread, owner) {
    this.thread = thread;
    this.rowElement = thread.tableCell.parentNode;
    this.owner = owner;
}

ThreadRow.prototype = {
    enterEditMode: function(captions) {
        $FX(this.thread.tableCell).find(".namfox-quick-caption-submit-icon").css("display", "inline");
        $FX(this.thread.tableCell).find(".namfox-quick-caption-edit-icon").css("display", "none");
        
        // Subject text box
        var subject = '<input class="namfox-quick-caption-subject" type="text" value="' + $.attrEncode(this.thread.subject) + '" />';
        var subjectContainer = $FX(this.thread.tableCell).find("a.subject");
        this.oldSubject = subjectContainer.find("b").html();
        this.subjectContainer = subjectContainer.outerHtml();
        this.subjectElement = subjectContainer.replaceWith(subject)
            .keypress($.createCallback(this.owner._onTextBoxKeyPress, this.owner, this))

        // Caption selection
        var captionHtml = '';
        captionHtml += '<div class="namfox-quick-caption ' + (captions && "drop-down") + '">';
        captionHtml += '  <div data-selectedIndex="0" class="namfox-quick-caption-selection selection">';
        captionHtml += '    <span class="namfox-caption-container">';
        captionHtml += '      <input class="namfox-quick-caption-caption" type="text"/>';
        captionHtml += '    </span>';
        captionHtml += '  </div>';
        captionHtml += '  <ul class="namfox-quick-caption-drop-down-list">';
        
        if (captions) {
            this.captions = captions;
            captionHtml += '<li data-index="0">';
            captionHtml += '  <span class="namfox-caption-container">';
            captionHtml += '    <input class="namfox-quick-caption-caption" type="text"/>';
            captionHtml += '  </span>';
            captionHtml += '</li>';

            for (var i = 0; i < captions.length; ++i) {
                var caption = captions[i];
                var innerHtml = caption.html.replace(
                    QuickCaption.textboxExpr,
                    '<input type="text" data-fieldName="$1" />'
                );

                captionHtml += '<li data-index="' + (i + 1) + '" data-autodeletion="' + (caption.autodeletion || -1) + '"><span class="namfox-caption-container">' + innerHtml + '</span></li>';
            }
        }

        captionHtml += '  </ul>';
        captionHtml += '</div>';

        var captionContainer = $FX(this.thread.tableCell).find("small");
        this.oldCaption = captionContainer.html();
        this.captionContainer = captionContainer.outerHtml();
        captionContainer = captionContainer.replaceWith(captionHtml);
        this.captionElement = captionContainer.find("div.namfox-quick-caption-selection");

        if (!captions) {
            captionContainer.find("ul").css("display", "none");
        }

        captionContainer.find("div.namfox-quick-caption-selection").click(function(event) {
            var list = captionContainer.find("ul");
            if (list.css("display") !== "block") {
                var selectedItem = captionContainer.find("div.namfox-quick-caption-selection");
                var selectedIndex = parseInt(selectedItem.attr("data-selectedIndex")) + 1;
                var clone = $FX.clone(selectedItem.find("span").get(0));

                list.find("li:eq(" + selectedIndex + ")").html("").append(clone)
                    .find("input")
                    .focus(onCaptionTextBoxFocused);

                list.css("display", "block");
            }
            else {
                list.css("display", "none");
            }
        });

        var that = this;
        captionContainer.find("li").click(function(event) {
            // Set this element as the selected item.
            captionContainer.find("li").attr("className", "");
            this.className = "selected";

            captionContainer.find("div.namfox-quick-caption-selection").attr("data-selectedIndex", $FX(this).attr("data-index"));

            var autodeletion = this.getAttribute("data-autodeletion");

            if (autodeletion !== "-1") {
                that.autodeletionElement.parent()
                    .find("li[data-autodeletion=" + autodeletion + "]")
                    .click();
            }

            // Update the span to contain the selected item
            var inputs = captionContainer.find("div.namfox-quick-caption-selection")
                .html("")
                .append($FX.clone($FX(this).find("span").get(0)))
                .find("input");

            if (inputs.length) {
                inputs.focus(onCaptionTextBoxFocused)
                    .click(function(event) {
                        // Prevent clicking on a text box from causing the drop down to expand.
                        event.stopPropagation();
                    })
                    .keypress($.createCallback(that.owner._onTextBoxKeyPress, that.owner, that))
                    .get(0)
                    .select();
            }

            // Prevent the menu from being hidden in the case of a double click.
            event.stopPropagation();

        }).dblclick(function(event) {
            that.owner._onFormSubmitted(event, that);

            // Stop the double click from reaching the table cell so it invokes quick caption again!
            event.stopPropagation();
        });

        var defaultCaptionElement = captionContainer.find("input")
            .focus(onCaptionTextBoxFocused)
            .keypress($.createCallback(this.owner._onTextBoxKeyPress, this.owner, this))
            .get(0);

        $FX(defaultCaptionElement)
            .click(function(event) {
                event.stopPropagation();
            })
            .get(0)
            .select();

        // Autodeletion selection
        var imgContainer = $FX(this.thread.tableCell).parent().find("td:first");
        var img = imgContainer.find("img");
        var html = '';
        html += '<div class="namfox-quick-caption-autodeletion drop-down">';
        html += '  <span class="namfox-quick-caption-autodeletion-selection selection" data-autodeletion="-1">';
        html +=      img.outerHtml();
        html += '  </span>';
        html += '  <ul>';
        html += '    <li class="selected" data-autodeletion="-1">Keep Existing</li>';
        html += '    <li data-autodeletion="0">Never delete</li>';
        html += '    <li data-autodeletion="2">Delete after 2 days</li>';
        html += '    <li data-autodeletion="7">Delete after 7 days</li>';
        html += '    <li data-autodeletion="30">Delete after 30 days</li>';
        html += '    <li data-autodeletion="90">Delete after 90 days</li>';
        html += '  </ul>';
        html += '</div>';

        this.autodeletionContainer = img.outerHtml();
        img.replaceWith(html);
        this.autodeletionElement = imgContainer.find("span");

        this.autodeletionElement.click(function(event) {
            var list = imgContainer.find("ul");
            if (list.css("display") !== "block") {
                list.css("display", "block");
            }
            else {
                list.css("display", "none");
            }
        });

        imgContainer.find("li").each(
            function(i, elem) {
                var backgroundImage = img.attr("src");
                if (i === 1) {
                    backgroundImage = QuickCaption.fromAutodeleteImage(backgroundImage);
                }
                else if (i > 1) {
                    backgroundImage = QuickCaption.toAutodeleteImage(backgroundImage);
                }

                elem.style.backgroundImage = "url('" + backgroundImage + "')";
                $FX(elem).attr("data-image", backgroundImage);
            });

        imgContainer.find("li").click(function(event) {
            // Set this element as the selected item.
            imgContainer.find("li").attr("className", "");
            this.className = "selected";

            // Update the image in the span
            var selection = imgContainer.find("span.namfox-quick-caption-autodeletion-selection");

            selection.attr("data-autodeletion", $FX(this).attr("data-autodeletion"));
            selection.find("img").attr("src", $FX(this).attr("data-image"));

            event.stopPropagation();
        });

        var that = this;
        function markupReceived(markup) {
            $FX("input.namfox-quick-caption-caption", that.rowElement.ownerDocument).val($.attrEncode(markup));
        }

        MarkupTranslator.getMarkupFromHtml(this.rowElement.ownerDocument, this.thread.caption, markupReceived);
    },

    hasChanges: function(callback) {
        var that = this;
        function markupReceived(markup) {
            callback(that.thread.autodeletion !== -1 || that.thread.caption !== markup || that.thread.subject !== that.oldSubject);
        }

        MarkupTranslator.getMarkupFromHtml(this.rowElement.ownerDocument, this.oldCaption, markupReceived);
    },

    updateThread: function() {
        this.thread.autodeletion = parseInt(this.autodeletionElement.attr("data-autodeletion"));
        this.thread.subject = this.subjectElement.attr("value");
        this.thread.needsCaptionHtml = true;

        var selectedIndex = this.captionElement.attr("data-selectedIndex");
        if (selectedIndex === "0") {
            this.thread.caption = this.captionElement.find("input").attr("value");
        }
        else {
            var caption = this.captions[parseInt(selectedIndex) - 1];
            var captionMarkup = caption.markup;
            var captionHtml = caption.html;

            var inputs = this.captionElement.find("input");
            if (!inputs.length) {
                this.thread.needsCaptionHtml = false;
            }

            inputs.each(function(i, input) {
                captionMarkup = captionMarkup.replace(new RegExp("#" + input.getAttribute("data-fieldName") + "#"), input.value);
                captionHtml = captionHtml.replace(new RegExp("#" + input.getAttribute("data-fieldName") + "#"), input.value);
            });

            this.thread.caption = captionMarkup;
            this.thread.captionHtml = caption.html;
            this.thread.bugCaptionValue = caption.value;
        }
    },

    updateUI: function(captionHtml) {
        $FX(this.thread.tableCell).find(".namfox-quick-caption-submit-icon").css("display", "none");
        $FX(this.thread.tableCell).find(".namfox-quick-caption-edit-icon").css("display", "inline");

        this.subjectElement.replaceWith(this.subjectContainer)
            .find("b")
            .html(this.thread.subject);

        this.captionElement.parent()
            .replaceWith(this.captionContainer)
            .html(captionHtml === undefined ? this.thread.oldCaption : captionHtml);

        var autodeletionImage = this.autodeletionElement.find("img").attr("src");
        this.autodeletionElement.parent()
            .replaceWith(this.autodeletionContainer)
            .attr("src", autodeletionImage);
    }
};

function onCaptionTextBoxFocused(event) {
    if ($FX.nodeName(event.target.parentNode.parentNode, "li")) {
        event.target.blur();
    }
};

// Templates
QuickCaption.bugTemplate = [
    {
        markup:"Status: [img]http://i.neoseeker.com/d/icons/flag_blue.png[/img] Bad Bugged",
        html:'Status: <img alt="" src="http://i.neoseeker.com/d/icons/flag_blue.png"/> Bad Bugged'
    },
    {
        markup:"Status: [img]http://i.neoseeker.com/d/icons/flag_black.png[/img] Closed",
        html:'Status: <img alt="" src="http://i.neoseeker.com/d/icons/flag_black.png"/> Closed'
    },
    {
        markup:"Status: [img]http://i.neoseeker.com/d/icons/flag_red.png[/img] Confirmed",
        html:'Status: <img alt="" src="http://i.neoseeker.com/d/icons/flag_red.png"/> Confirmed'
    },
    {
        markup:"Status: [img]http://i.neoseeker.com/d/icons/flag_yellow.png[/img] Fix Pending",
        html:'Status: <img alt="" src="http://i.neoseeker.com/d/icons/flag_yellow.png"/> Fix Pending'
    },
    {
        markup:"Status: [img]http://i.neoseeker.com/d/icons/flag_purple.png[/img] Redirected",
        html:'Status: <img alt="" src="http://i.neoseeker.com/d/icons/flag_purple.png"/> Redirected'
    },
    {
        markup:"Status: [img]http://i.neoseeker.com/d/icons/flag_green.png[/img] Resolved",
        html:'Status: <img alt="" src="http://i.neoseeker.com/d/icons/flag_green.png"/> Resolved'
    },
    {
        markup:"Status: [img]http://i.neoseeker.com/d/icons/flag_orange.png[/img] Unconfirmed",
        html:'Status: <img alt="" src="http://i.neoseeker.com/d/icons/flag_orange.png"/> Unconfirmed'
    }
];

QuickCaption.namfoxTemplate = [
    {
        markup:"Status: [img]http://i.neoseeker.com/d/icons/flag_black.png[/img] Closed – As Designed",
        html:'Status: <img alt="" src="http://i.neoseeker.com/d/icons/flag_black.png"/> Closed – As Designed'
    },
    {
        markup:"Status: [img]http://i.neoseeker.com/d/icons/flag_black.png[/img] Closed – Deferred",
        html:'Status: <img alt="" src="http://i.neoseeker.com/d/icons/flag_black.png"/> Closed – Deferred'
    },
    {
        markup:"Status: [img]http://i.neoseeker.com/d/icons/flag_black.png[/img] Closed – Duplicate",
        html:'Status: <img alt="" src="http://i.neoseeker.com/d/icons/flag_black.png"/> Closed – Duplicate'
    },
    {
        markup:"Status: [img]http://i.neoseeker.com/d/icons/flag_black.png[/img] Closed – Fixed",
        html:'Status: <img alt="" src="http://i.neoseeker.com/d/icons/flag_black.png"/> Closed – Fixed'
    },
    {
        markup:"Status: [img]http://i.neoseeker.com/d/icons/flag_black.png[/img] Closed – Obsolete",
        html:'Status: <img alt="" src="http://i.neoseeker.com/d/icons/flag_black.png"/> Closed – Obsolete'
    },
    {
        markup:"Status: [img]http://i.neoseeker.com/d/icons/flag_black.png[/img] Closed – Unable to Reproduce",
        html:'Status: <img alt="" src="http://i.neoseeker.com/d/icons/flag_black.png"/> Closed – Unable to Reproduce'
    },
    {
        markup: "Status: [img]http://i.neoseeker.com/d/icons/flag_red.png[/img] Confirmed – P#PRIORITY#, S#SEVERITY#",
        html: 'Status: <img alt="" src="http://i.neoseeker.com/d/icons/flag_red.png"/> Confirmed – P#PRIORITY#, S#SEVERITY#' 
    },
    {
        markup: "Status: [img]http://i.neoseeker.com/d/icons/flag_green.png[/img] Implemented (#VERSION#)",
        html: 'Status: <img alt="" src="http://i.neoseeker.com/d/icons/flag_green.png"/> Implemented (#VERSION#)' 
    },
    {
        markup: "Status: [img]http://i.neoseeker.com/d/icons/flag_purple.png[/img] Not Under Consideration",
        html: 'Status: <img alt="" src="http://i.neoseeker.com/d/icons/flag_purple.png"/> Not Under Consideration' 
    },
    {
        markup: "Status: [img]http://i.neoseeker.com/d/icons/flag_green.png[/img] Resolved (#VERSION#)",
        html: 'Status: <img alt="" src="http://i.neoseeker.com/d/icons/flag_green.png"/> Resolved (#VERSION#)' 
    },
    {
        markup:"Status: [img]http://i.neoseeker.com/d/icons/flag_orange.png[/img] Unconfirmed",
        html:'Status: <img alt="" src="http://i.neoseeker.com/d/icons/flag_orange.png"/> Unconfirmed'
    },
    {
        markup: "Status: [img]http://i.neoseeker.com/d/icons/flag_red.png[/img] Under Consideration",
        html: 'Status: <img alt="" src="http://i.neoseeker.com/d/icons/flag_red.png"/> Under Consideration' 
    },
    {
        markup: "Status: [img]http://i.neoseeker.com/d/icons/flag_blue.png[/img] Will Not Implement",
        html: 'Status: <img alt="" src="http://i.neoseeker.com/d/icons/flag_blue.png"/> Will Not Implement' 
    }
];

QuickCaption.suggestionTemplate = [
    {
        markup:"Status: [img]http://i.neoseeker.com/d/icons/flag_green.png[/img] Implemented",
        html:'Status: <img alt="" src="http://i.neoseeker.com/d/icons/flag_green.png"/> Implemented'
    },
    {
        markup:"Status: [img]http://i.neoseeker.com/d/icons/flag_yellow.png[/img] Work In Progress",
        html:'Status: <img alt="" src="http://i.neoseeker.com/d/icons/flag_yellow.png"/> Work In Progress'
    },
    {
        markup:"Status: [img]http://i.neoseeker.com/d/icons/flag_orange.png[/img] #CUSTOM#",
        html:'Status: <img alt="" src="http://i.neoseeker.com/d/icons/flag_orange.png"/> #CUSTOM#'
    },
    {
        markup:"Status: [img]http://i.neoseeker.com/d/icons/flag_red.png[/img] Support Needed",
        html:'Status: <img alt="" src="http://i.neoseeker.com/d/icons/flag_red.png"/> Support Needed'
    },
    {
        markup:"Status: [img]http://i.neoseeker.com/d/icons/flag_blue.png[/img] Redirected",
        html:'Status: <img alt="" src="http://i.neoseeker.com/d/icons/flag_blue.png"/> Redirected'
    },
    {
        markup:"Status: [img]http://i.neoseeker.com/d/icons/flag_purple.png[/img] #CUSTOM#",
        html:'Status: <img alt="" src="http://i.neoseeker.com/d/icons/flag_purple.png"/> #CUSTOM#'
    },
    {
        markup:"Status: [img]http://i.neoseeker.com/d/icons/flag_black.png[/img] No Hope!",
        html:'Status: <img alt="" src="http://i.neoseeker.com/d/icons/flag_black.png"/> No Hope!'
    }
];

QuickCaption.forumSiteBugReportsTemplate = [
    {
        markup:"Status: [img]http://i.neoseeker.com/d/icons/flag_orange.png[/img] Unconfirmed",
        html:'Status: <img alt="" src="http://i.neoseeker.com/d/icons/flag_orange.png"/> Unconfirmed',
        value:'Unconfirmed'
    },
    {
        markup:"Status: [img]http://i.neoseeker.com/d/icons/flag_red.png[/img] Confirmed",
        html:'Status: <img alt="" src="http://i.neoseeker.com/d/icons/flag_red.png"/> Confirmed',
        value:'Confirmed'
    },
    {
        markup:"Status: [img]http://i.neoseeker.com/d/icons/flag_yellow.png[/img] Fix Pending",
        html:'Status: <img alt="" src="http://i.neoseeker.com/d/icons/flag_yellow.png"/> Fix Pending',
        value:'Fix Pending'
    },
    {
        markup:"Status: [img]http://i.neoseeker.com/d/icons/flag_green.png[/img] Resolved – Please Confirm",
        html:'Status: <img alt="" src="http://i.neoseeker.com/d/icons/flag_green.png"/> Resolved – Please Confirm',
        value:'Resolved'
    },
    {
        markup:"Status: [img]http://i.neoseeker.com/d/icons/flag_black.png[/img] Closed – Fixed",
        html:'Status: <img alt="" src="http://i.neoseeker.com/d/icons/flag_black.png"/> Closed – Fixed',
        value:'Closed - Fixed'
    },
    {
        markup:"Status: [img]http://i.neoseeker.com/d/icons/flag_black.png[/img] Closed – Unreproducible",
        html:'Status: <img alt="" src="http://i.neoseeker.com/d/icons/flag_black.png"/> Closed – Unreproducible',
        value:'Closed - Unreproducible'
    },
    {
        markup:"Status: [img]http://i.neoseeker.com/d/icons/flag_black.png[/img] Closed – Duplicate",
        html:'Status: <img alt="" src="http://i.neoseeker.com/d/icons/flag_black.png"/> Closed – Duplicate',
        value:'Closed - Duplicate'
    },
    {
        markup:"Status: [img]http://i.neoseeker.com/d/icons/flag_black.png[/img] Closed – Won't Fix",
        html:'Status: <img alt="" src="http://i.neoseeker.com/d/icons/flag_black.png"/> Closed – Won\'t Fix',
        value:"Closed - Won't Fix"
    },
    {
        autodeletion: 7,
        markup:"Status: [img]http://i.neoseeker.com/d/icons/flag_blue.png[/img] Bad Bugged",
        html:'Status: <img alt="" src="http://i.neoseeker.com/d/icons/flag_blue.png"/> Bad Bugged',
        value:'Bad Bugged'
    }
];

QuickCaption.moderatorPromotions = [
    {
        markup:"Status: [color=green][b]Approved[/b][/color]",
        html:'Status: <span style="color:green;"><strong>Approved</strong></span>'
    },
    {
        markup:"Status: [color=green][b]Approved[/b][/color] - 4 days",
        html:'Status: <span style="color:green;"><strong>Approved</strong></span> - 4 days'
    },
    {
        markup:"Status: [color=green][b]Approved[/b][/color] - 7 days",
        html:'Status: <span style="color:green;"><strong>Approved</strong></span> - 7 days'
    },
    {
        markup:"Status: [color=green][b]Approved[/b][/color] - 30 days",
        html:'Status: <span style="color:green;"><strong>Approved</strong></span> - 30 days'
    },
    {
        markup:"Status: [color=red][b]Denied[/b][/color]",
        html:'Status: <span style="color:red;"><strong>Denied</strong></span>'
    }
];

QuickCaption.neoWikiBugs = QuickCaption.bugTemplate.concat(
    [
        {
            markup: "[img]http://i.neoseeker.com/d/icons/flag_red.png[/img] Confirmed - [b]CRITICAL[/b]",
            html: 'Status: <img alt="" src="http://i.neoseeker.com/d/icons/flag_red.png"/> Confirmed - <b>CRITICAL</b>'
        },
        {
            markup: "[img]http://i.neoseeker.com/d/icons/flag_orange.png[/img] Unconfirmed - [b]CRITICAL[/b]",
            html: 'Status: <img alt="" src="http://i.neoseeker.com/d/icons/flag_orange.png"/> Unconfirmed - <b>CRITICAL</b>'
        }
    ]
);

QuickCaption.wikiStaffApplications = [
    {
        markup: "[img]http://i.neoseeker.com/d/icons/flag_green.png[/img]  [b]Promoted[/b] - (#TEXT#)",
        html: '<img alt="" src="http://i.neoseeker.com/d/icons/flag_green.png" /> <strong>Promoted</strong> - (#TEXT#)'
    },
    {
        markup: "[img]http://i.neoseeker.com/d/icons/flag_yellow.png[/img]  [b]Pending[/b]",
        html: '<img alt="" src="http://i.neoseeker.com/d/icons/flag_yellow.png" /> <strong>Pending</strong>'
    },
    {
        markup: "[img]http://i.neoseeker.com/d/icons/flag_yellow.png[/img]  [b]Needs More Input[/b]",
        html: '<img alt="" src="http://i.neoseeker.com/d/icons/flag_yellow.png" /> <strong>Needs More Input</strong>'
    },
    {
        markup: "[img]http://i.neoseeker.com/d/icons/flag_red.png[/img]  [b]Denied[/b] - (#TEXT#)",
        html: '<img alt="" src="http://i.neoseeker.com/d/icons/flag_red.png" /> <strong>Denied</strong> - (#TEXT#)'
    },
    {
        markup: "[img]http://i.neoseeker.com/d/icons/flag_black.png[/img]  [b]Denied[/b] - Blacklisted",
        html: '<img alt="" src="http://i.neoseeker.com/d/icons/flag_black.png" /> <strong>Denied</strong> - Blacklisted' 
    },
    {
        markup: "[img]http://i.neoseeker.com/d/icons/flag_purple.png[/img]  [b]Retracted[/b]",
        html: '<img alt="" src="http://i.neoseeker.com/d/icons/flag_purple.png" /> <strong>Retracted</strong>'
    },
    {
        markup: "[img]http://i.neoseeker.com/d/icons/flag_green.png[/img]  [b]Demoted[/b] - Retired",
        html: '<img alt="" src="http://i.neoseeker.com/d/icons/flag_green.png" /> <strong>Demoted</strong> - Retired'
    },
    {
        markup: "(#TEXT#)",
        html: '(#TEXT#)'
    }
];

QuickCaption.forumCaptions = {

    /// <summary>Forum bug reports</summary>
    "29": QuickCaption.forumSiteBugReportsTemplate,

    /// <summary>Site bug reports</summary>
    "30": QuickCaption.forumSiteBugReportsTemplate,

    /// <summary>FAQ/VFAQ Authors and Feedback Forum</summary>
    "61": QuickCaption.bugTemplate,

    /// <summary>NAMFox forum</summary>
    "115": QuickCaption.namfoxTemplate,

    /// <summary>NeoWiki Bugs</summary>
    "22152": QuickCaption.neoWikiBugs,

    /// <summary>NeoPortal Discussion, Suggestions and Feedback</summary>
    "25323": QuickCaption.suggestionTemplate,

    /// <summary>NeoPortal Requests and Planning</summary>
    "25370": QuickCaption.suggestionTemplate,

    /// <summary>Nxchange Bugs and Suggestions</summary>
    "27101": Array.concat(QuickCaption.bugTemplate, QuickCaption.suggestionTemplate),

    /// <summary>Moderator promotions system.</summary>
    "33797": QuickCaption.moderatorPromotions,

    /// <summary>NEOCRS Private Bug Forum</summary>
    "35490": QuickCaption.bugTemplate,

    /// <summary>GameGrep Feedback & Suggestions</summary>
    "35629": QuickCaption.suggestionTemplate,

    /// <summary>GameGrep Bugs</summary>
    "35630": QuickCaption.bugTemplate,

    /// <summary>NEOCRS Bugs and Feedback Forum</summary>
    "38689": QuickCaption.bugTemplate,

    /// <summary>Videos Bugs and Feedback Forum</summary>
    "39044": QuickCaption.bugTemplate,

    /// <summary>Archived Site Bug Reports</summary>
    "39294": QuickCaption.bugTemplate,

    /// <summary>Archived Forum Bug Reports</summary>
    "39295": QuickCaption.bugTemplate,

    /// <summary>NAMFox for Moderators</summary>
    "39757": QuickCaption.namfoxTemplate,

    /// <summary>Wiki Staff Applications</summary>
    "41744": QuickCaption.wikiStaffApplications
};

}});